import * as puppeteer from 'puppeteer-core';
import { authenticator } from 'otplib';
import { DomNotFound } from './exception';
import { installMouseHelper } from './utils/mouse-helper';

async function startBrowser(userId: string) {
  const query = new URLSearchParams();
  query.set('user_id', userId);
  query.set('open_tabs', '1');
  query.set('headless', '0');
  query.set("launch_args", '["--disable-notifications"]')
  // 禁用密码填充
  query.set('disable_password_filling', '1');
  // 关闭浏览器后是否清除缓存
  query.set('clear_cache_after_closing', '0');
  // 是否允许保存密码
  query.set('enable_password_saving', '0')
  const apiUrl = "http://127.0.0.1:50325/api/v1/browser/start?" + query.toString();
  const rs = await fetch(apiUrl);
  const resp = await rs.json();

  console.log(resp);
  console.log(resp.data.ws);
  console.log(resp.data.ws.puppeteer);
  return resp.data.ws.puppeteer;
}

async function sleep(ms: number) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

abstract class PageAction {
  browser: puppeteer.Browser;
  page: puppeteer.Page;
  mouse: puppeteer.Mouse;
  keyboard: puppeteer.Keyboard;

  constructor(page: puppeteer.Page) {
    this.page = page;
    this.browser = page.browser();
    this.mouse = page.mouse;
    this.keyboard = page.keyboard;
  }

  public roffset(x: number) {
    //
  }

  // 睡眠一段时间 按毫秒即时，rnd 未随机
  public rsleep(ms: number): Promise<void> {
    const x1 = Math.random() > 0.5 ? 1 : -1;
    const x2 = Math.random();
    const x = ms * (1 + x1 * x2);
    return new Promise((resolve, reject) => {
      console.log(`rsleep ${x}, x1: ${x1}, x2: ${x2}, x: ${x}`);
      setTimeout(resolve, x);
    });
  }

  public async moveAndClick(x: number, y: number, steps?: number) {
    await this.mouse.move(x, y, { steps: steps });
    await this.mouse.click(x, y);
  }

  public async clickOnElement<T extends Node>(e: puppeteer.ElementHandle<T>) {
    const pos = await e.clickablePoint();
    await this.mouse.move(pos.x, pos.y, { steps: 5 });
    await this.mouse.click(pos.x, pos.y);
  }

  public abstract action(args: any): Promise<any>;
}

/**
 * 执行简单的 fb 帐号登录，浏览帖子，并在一定时间后，重定向到正常web页的操作
 * @param wsUrl 调试URL
 */
async function fbLogin(wsUrl: string, userName: string, pwd: string, twofa: string) {
  const browser = await puppeteer.connect({
    browserWSEndpoint: wsUrl,
    defaultViewport: null,
    slowMo: 150,
  });
  // 拒绝所有的消息询问弹窗
  const ctx = browser.defaultBrowserContext();
  await ctx.overridePermissions("https://facebook.com", ["notifications"])
  console.log(`connect finished.`);
  const page = await browser.newPage();
  // const pages = await browser.pages();
  // console.log(pages);
  // for (let i = 0; i !== pages.length; i++) {
  //   const title = await pages[i].title();
  //   console.log(`${i}, ${title}`);
  // }
  // const page = pages[0];
  // page.bringToFront();
  await installMouseHelper(page);
  const rs = await page.goto("https://mbasic.facebook.com/");
  console.log(rs);
  console.log('page load finished');
  const a1 = new FbLogin(page);
  await a1.action({ userName, pwd });
  console.log('login finished');

  // 二次验证 2fa
  const twopwd = new TwoFa(page);
  await twopwd.action({ key: twofa });
  console.log('twofa finished');

  // 点击不要记住 2fa
  const dontsave = new RememberBrowser(page);
  await dontsave.action();
  console.log('dont save finished');

  // 滚动测试
  const scroll = new RndScroll(page);
  await scroll.action();

  // 查找 login in 按钮
  console.log('finished.');
}

class RememberBrowser extends PageAction {
  public async action(): Promise<any> {
    await this.page.waitForNavigation();
    console.log('navigation finished...');
    await this.rsleep(400);
    await this.clickDontSave();
    await this.rsleep(500);
    await this.clickSubmit();
    return 1;
  }

  private async clickDontSave() {
    const i0 = await this.page.waitForSelector("input[value='dont_save']");
    if (!i0) {
      throw new DomNotFound("input[value='dont_save'] not found!");
    }
    await this.clickOnElement(i0);
  }

  private async clickSubmit() {
    const i0 = await this.page.waitForSelector("#checkpointSubmitButton-actual-button");
    if (!i0) {
      throw new DomNotFound("#checkpointSubmitButton-actual-button not found");
    }
    await this.clickOnElement(i0);
  }
}

class TwoFa extends PageAction {
  public async action(args: { key: string }): Promise<any> {
    await this.page.waitForNavigation();
    console.log('navigation finished!');
    await this.input2Fa(args.key);
    await this.clickSubmit();
    return 1;
  }

  private twofa(key: string) {
    return authenticator.generate(key);
  }

  private async input2Fa(key: string) {
    const i0 = await this.page.waitForSelector("#approvals_code");
    if (!i0) {
      throw new DomNotFound('#approvals_code not found');
    }
    const pos = await i0.clickablePoint();
    await this.moveAndClick(pos.x, pos.y, 5);
    const token = this.twofa(key);
    console.log(`token: ${token}`);
    await this.keyboard.type(token);
  }

  private async clickSubmit() {
    const i0 = await this.page.waitForSelector("#checkpointSubmitButton-actual-button");
    if (!i0) {
      throw new DomNotFound("#checkpointSubmitButton-actual-button not found");
    }
    await this.clickOnElement(i0);
  }
}

class RndScroll extends PageAction {
  public async action(): Promise<void> {
    await this.page.waitForNavigation();
    // 鼠标移动到屏幕中间
    const size = await this.page.evaluate(() => {
      return {
        width: window.innerWidth,
        height: window.innerHeight,
      }
    });
    console.log(size);
    await this.mouse.move(size.width / 2, size.height / 2, { steps: 5 });
    // // 循环
    for (let i = 0; i != 50; i++) {
      // 滚动
      await this.mouse.wheel({
        deltaY: 100,
      });
      // 暂停
      await this.rsleep(100);
      // 检查 See more stories 按钮是否可见 如可见，则直接点
    }
    console.log('click more.');
    await this.checkAndClickMore();
    // 等三分钟左右
    await this.mouse.move(size.width / 2, size.height / 2, { steps: 5 });
    await this.mouse.wheel({
      deltaY: 100,
    });
    await sleep(60 * 3 * 1000);
    await this.page.goto("https://facebook.com/");
  }

  private async checkAndClickMore() {
    const spans = await this.page.$x("//span[contains(text(), 'See more stories')]");
    if (spans.length === 0) {
      return;
    }
    const span = spans[0];
    const pos = await span.clickablePoint({ x: -100, y: 0 });
    await this.moveAndClick(pos.x, pos.y);
  }
}

class FbLogin extends PageAction {
  private async inputUser(userName: string) {
    const el = await this.page.waitForSelector("#m_login_email");
    if (!el) {
      throw new DomNotFound('m_login_email not found');
    }
    await sleep(1 * 1000);
    // 查找元素位置 并点击中心处
    let pos = await el.clickablePoint();
    await this.mouse.move(pos.x, pos.y);
    await this.mouse.click(pos.x, pos.y);
    // 输入用户名并略等
    await this.page.keyboard.type(userName);
    await sleep(0.1 * 1000);
  }

  // 查找密码输入框
  private async inputPwd(pwd: string) {
    const pwdEl = await this.page.waitForSelector("input[type='password']");
    if (!pwdEl) {
      throw new DomNotFound("input[type='password'] not found");
    }
    const pos = await pwdEl.clickablePoint();
    await this.mouse.move(pos.x, pos.y);
    await this.mouse.click(pos.x, pos.y);
    await this.page.keyboard.type(pwd);
    await sleep(0.2 * 1000);
  }

  private async clickLoginBtn() {
    const btn = await this.page.waitForSelector("input[name='login']");
    if (!btn) {
      throw new DomNotFound("input[name='login'] not found");
    }
    const pos = await btn.clickablePoint();
    await this.mouse.move(pos.x, pos.y, { steps: 5 });
    await this.mouse.click(pos.x, pos.y);
  }

  public async action(args: { userName: string; pwd: string }): Promise<unknown> {
    await this.inputUser(args.userName);
    await this.inputPwd(args.pwd);
    await this.clickLoginBtn();
    return 1;
  }
}


async function main() {
  // 1 => jbhtcuv
  // 2 => jbjrtx8
  const pUrl = await startBrowser('jbhtcuv');

  // const pUrl = 'ws://127.0.0.1:50706/devtools/browser/4ed91670-5a48-4d2f-92be-7fe7b5a3b712';
  const userName = "100010984201576";
  const pwd = "usasdzxcqwe123";
  const twoKey = "WUQMBJIVP2AVEIH3M6BDE72BDBAPTDBC";
  await fbLogin(pUrl, userName, pwd, twoKey);
}

async function test1() {
  const pUrl = await startBrowser('jbhtcuv');
  const browser = await puppeteer.connect({
    browserWSEndpoint: pUrl,
    defaultViewport: null,
    slowMo: 150,
  });
  // const ctx = browser.defaultBrowserContext();
  // await ctx.overridePermissions("https://facebook.com", ["notifications"])
  const page = await browser.newPage();
  await page.goto("http://127.0.0.1:8080/");
  console.log('OK');
  const context = browser.defaultBrowserContext();

  await page.goto('https://serviceworke.rs/push-simple_demo.html');
  console.log(await page.evaluate(function () { return Notification.requestPermission(); }));
  console.log(await page.evaluate(function () { return Notification.permission; }));

  await context.overridePermissions('https://serviceworke.rs', ['notifications']);

  await page.goto('https://serviceworke.rs/push-simple_demo.html');
  console.log(await page.evaluate(function () { return Notification.requestPermission(); }));
  console.log(await page.evaluate(function () { return Notification.permission; }));

  await browser.close();
}

async function test2() {
  const pUrl = await startBrowser('jbjrtx8');
  const browser = await puppeteer.connect({
    browserWSEndpoint: pUrl,
    defaultViewport: null,
    slowMo: 150,
  });
  const page = await browser.newPage();
  await page.goto("https://www.hao123.com/");
}

main();
// test2();
